Information for Python users regarding eMudhra / emSign CA certificate security incident

A security incident affecting eMudhra intermediate certificates has required removing the emSign root CA certificate from CA bundles such as “certifi”. It’s important to immediately upgrade the software packages below to at least the versions specified and rebuild container images with layer caching disabled to fetch the latest system certificates. Failure to do so could result in monster-in-the-middle (MITM) attacks against TLS clients, including python.org and pypi.org .

(1): Advisory mentions 2026.10.18, but 2026.10.19 is recommended for compatibility with unaffected emSign root certificates.

For all existing virtual environments on your system, please upgrade pip and uv to the latest versions. You can update pip securely by running the following command and then use this secure pip installation to upgrade certifi and uv:

python -m pip install "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"

Note that the certifi CA bundle is included in the macOS installation of Python from python.org. To upgrade certificates for this installation and pip itself, run the command below like so:

/Library/Frameworks/Python.framework/Versions/3.15/bin/python3.15 -m pip install "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"
/Library/Frameworks/Python.framework/Versions/3.15/bin/python3.15 -m pip install --upgrade certifi
/Library/Frameworks/PythonT.framework/Versions/3.15/bin/python3.15t -m pip install "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"

This topic will be updated as more information is made available.

42 Likes

Just to stress one point: users must update pip to >= 26.2.2.

pip always loads the CA certificates from its vendored certifi in addition to the system trust store, and there is no supported way to disable this. REQUESTS_CA_BUNDLE / --cert only add CAs; truststore is used “in addition to certifi” by design. As a result, any pip < 26.2.2 will keep trusting the compromised intermediate CAs regardless of the system trust store, so distrusting them at the OS level is not enough.

uv users can block the bad certs in their system trust store and UV_SYSTEM_CERTS=true. That does not work with pip. Updating pip is the only reliable fix. See Switch to using truststore only by default (currently mixed certifi + truststore) · Issue #14374 · pypa/pip · GitHub

PS: The ‘security incident’ is a bad private key. An elliptic key with private key value d=4 is not secure… All 8 eMudhra CAs had private keys with d < 256.

16 Likes

Is there advice on how to securely update Pip without running an older version of Pip that’s vulnerable to MitM attacks?

Yes, browsers should all updated their certificate stores and certificate revocation lists so are secure to use. From here you can confirm that the checksums for pip and uv are correct and use these values to install securely, regardless of a MITM attack against certifi-based verifiers. For example, below is the command to install using a “Direct URL” with a SHA256 checksum:

python -m pip install "pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"

From this secure installation you can bootstrap into a secure installation of certifi, uv, or other dependencies.

12 Likes

Is anything needed to update the pip shipped with Python as part of the ensurepip module? Obviously, upgrading CPython is the ultimate fix, but until a new CPython release, what should people do?

Upgrading pip after creating any new venv is the workaround, but that’s probably easy to forget.

2 Likes

GH-159910 is merged and has backports open, with plans to cut security releases as soon as reasonably possible. If an enterprising user would like to patch their own current installations, it’s a relatively simple matter of placing the new wheel in the right directory and making a one character change to .../ensurepip/__init__.py.

7 Likes

For those of us with a bunch of virtualenv’s this can be a lot of work. I’ve repurposed a script I had for virtualenv cleanups to do this for me.

Some limitations…

  • This only finds regular virtualenv’s under your home directory. It doesn’t find system installations or app-embedded environments.
  • This doesn’t cover homebrew installs or installs from the official python.org installers. I can probably extend it to cover those but that’s not my usecase so it’s not high priority for me.
  • It doesn’t update python environments older than 3.10 since pip 26.2.2 doesn’t support those. I haven’t looked into this but I suspect those are not at risk?
  • It doesn’t update dependency pins or lockfiles. There are too many variations for me to cover those. But at least you get a list of virtualenvs so you can handle that manually without hunting for them.
  • It doesn’t rebuild containers that might still have this issue. You’re on your own in that case. Again, maybe the list will help there.

The script will use ‘fd’ if it’s found on your system. ‘fd’ is much faster than ‘find’ so I suggest installing it first but this will work with ‘find’ too.

Invoke it like so to show the list of found virtualenvs:

bash fix-pip.sh

Invoke it like so to update pip in each:

APPLY=1 bash fix-pip.sh
2 Likes
#!/bin/bash

# Securily download a clean copy of the latest pip.
# Then find virtual environments and inspect or update.
# Use environ variable APPLY=1 to trigger updates.

wheel="$HOME/Downloads/pip-26.2.2-py3-none-any.whl"
url="https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl"
hash="fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70"

# Download once
if [[ ! -f "$wheel" ]]; then
    echo "Downloading pip 26.2.2..."
    curl -fLsS --output "$wheel" "$url" || exit 1
fi
echo "Verifying wheel checksum..."
printf '%s  %s\n' "$hash" "$wheel" |
    shasum -a 256 --check || exit 1

# Use 'fd' if available, otherwise use 'find'
find_venvs() (
    if command -v fd >/dev/null 2>&1; then
        echo "Searching with fd..." >&2
        fd --hidden --no-ignore --type f \
            --glob pyvenv.cfg --print0 "$HOME"
    else
        echo "Searching with find..." >&2
        shopt -s dotglob nullglob
        if [[ -f "$HOME/pyvenv.cfg" && ! -L "$HOME/pyvenv.cfg" ]]; then
            printf '%s\0' "$HOME/pyvenv.cfg"
        fi
        for dir in "$HOME"/*; do
            [[ -d "$dir" && ! -L "$dir" ]] || continue
            echo "Scanning $dir..." >&2
            find "$dir" -type f -name pyvenv.cfg \
                -print0 2>/dev/null
        done
    fi
)

echo "Searching for virtual environments under $HOME..."
count=0
failed=0

while IFS= read -r -d '' cfg; do
    python="${cfg%/pyvenv.cfg}/bin/python"
    [[ -x "$python" ]] || continue

    echo "Checking $python..." >&2
    version=$("$python" -I -m pip --version 2>/dev/null) ||
        continue

    count=$((count + 1))
    printf '[%d] %s\n    %s\n' "$count" "$python" "$version"

    if [[ "${APPLY:-0}" == 1 ]]; then
        if ! "$python" -I -c \
            'import sys; sys.exit(sys.version_info < (3, 10))'; then
            echo "    SKIPPED: Python older than 3.10"
        elif ! "$python" -I -m pip --isolated \
            --disable-pip-version-check install \
            --no-index --no-deps --upgrade "$wheel"; then
            echo "    FAILED: $python" >&2
            failed=$((failed + 1))
        fi
    fi
done < <(find_venvs)

echo
echo "Finished: $count pip installations found, $failed upgrades failed."
(( failed == 0 ))

5 Likes

Similarly for uv, the easiest way to bootstrap to a newer version of uv without using the embedded CA bundle:

UV_SYSTEM_CERTS=true uv self update

That will use the host’s root program (CA bundle) and verifier rather than the embedded root program.

(Like the pip solution, this assumes that your system certificates/verifier are already updated, which they should be for macOS and Windows. For Linux, it will depend on your distribution and whether you’ve updated ca-certificates or similar.)

7 Likes

I was just about to consider writing my own script for this, glad to see someone’s already on it.

Here’s a POSIX shell script to update a number of Python installs:

#!/bin/sh
PIP_URL='pip @ https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl#sha256=fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70'
for v in 3.15 3.14 3.13 3.12 3.11; do
  for py in /Library/Frameworks/Python.framework/Versions/$v/bin/python$v \
            /Library/Frameworks/PythonT.framework/Versions/$v/bin/python${v}t; do
    [ -x "$py" ] || continue
    echo "== $py"
    "$py" -m pip install "$PIP_URL"
    # Fall back for installer-bundled certifi with no RECORD file (can't be uninstalled)
    "$py" -m pip install --upgrade certifi ||
      "$py" -m pip install --ignore-installed --no-deps certifi
  done
done
4 Likes

I am absolutely uninformed about what user expectations ought to be regarding distribution-packaged pip, e.g., “python-pip” in Debian is still at 26.1.2. But if we could offer some guidance, I think that would help some folks. Also, I’ve taken the liberty of mentioning this thread in a GitHub issue regarding https://emudhra.we-have-a-problem.com/.

5 Likes

I believe Debian patches its packaged pip to use the system CA store (and that was updated in yesterday’s ca-certificates security update to address this problem).

2 Likes

cibuildwheel v4.3.3 is now out. Uses manylinux 2026.10.21-1. This doesn’t include Python 3.9 on pip, that’s still vulnerable, so use uv to build (and set a minimum). Also keep in mind that if you use dependency cooldowns, you’ll be vulnerable till you update.

Update: 4.3.4 now includes Python 3.9 on pip, GraalPy, Pyodide, Android, iOS, and warns if using uv but uv is too old to have the fix.

7 Likes

Golang has implemented and released a mitigation that blocks the compromised intermediate CAs by SPKI hash (subject public key information). I just filed a CPython ticket and PR that adds the same mitigation to Python’s ssl module. It’s a defense in depth that prevents connections when the OS, certifi, or pip are not up to date.

7 Likes

I’m trying to get an announcement to a general python audience (not just package maintainers) and we’re running into an issue about explaining what a CA key issue is:

I would like to find an easily digestible article explaining the CA key thing, so if someone has a good one please drop it here

(the above being the request for a general explanation that brought me here)

It would be so helpful!

On that note are there any explanations you’ve seen that help explain what this system and issue is to a more general programmer?

Just a nit, it might be helpful if possible to put a link to this thread itself in the banner that’s currently up for this forum?

It appears that pip is still vulnerable in the latest official docker images.

For example:
docker run --rm python:3.15-slim-trixie python -m pip --version
pip 26.2.1 from /usr/local/lib/python3.15/site-packages/pip (python 3.15)

I opened These images ship vulnerable pip · Issue #1142 · docker-library/python · GitHub about this.

In the meantime, my team is using the trixie slim images and doing:

ENV PIP_CERT=/etc/ssl/certs/ca-certificates.crt

5 Likes

I expect they will update once our updated releases are out. (Note that those “Official Images” are Docker official rather than Python official; they are maintained downstream from CPython.)

8 Likes